<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSE 服务端检测工具</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .container {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }
        .input-group {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }
        textarea {
            height: 100px;
            resize: vertical;
        }
        button {
            padding: 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
        .log-container {
            border: 1px solid #ccc;
            padding: 10px;
            height: 300px;
            overflow-y: auto;
            background-color: #f5f5f5;
        }
        .log-entry {
            margin: 5px 0;
            padding: 5px;
            border-bottom: 1px solid #eee;
        }
        .error {
            color: red;
        }
        .success {
            color: green;
        }
        .header-row {
            display: flex;
            gap: 10px;
            margin-bottom: 5px;
            align-items: center;
        }
        .header-name, .header-value {
            flex: 1;
        }
        .remove-header {
            background-color: #f44336;
            color: white;
            border: none;
            padding: 5px 10px;
            cursor: pointer;
        }
        .add-header {
            margin-top: 5px;
            align-self: flex-start;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>SSE 服务端检测工具</h1>
        
        <div class="input-group">
            <label for="url">服务端地址：</label>
            <input type="text" id="url" placeholder="https://example.com/sse" value="http://localhost:3000/sse">
        </div>

        <div class="input-group">
            <label for="method">请求方法：</label>
            <select id="method">
                <option value="GET">GET</option>
                <option value="POST">POST</option>
                <option value="PUT">PUT</option>
                <option value="DELETE">DELETE</option>
                <option value="PATCH">PATCH</option>
            </select>
        </div>

        <div class="input-group">
            <label>请求头：</label>
            <div id="headers-container">
                <!-- 头部会动态添加 -->
            </div>
            <button class="add-header" id="add-header">添加请求头</button>
        </div>

        <div class="input-group">
            <label for="body">请求体（可选，仅适用于 POST/PUT/PATCH 等方法）：</label>
            <textarea id="body"></textarea>
        </div>

        <button id="connect">连接</button>
        <button id="disconnect" disabled>断开连接</button>

        <div class="log-container" id="log"></div>
    </div>

    <script>
        let eventSource = null;
        const logContainer = document.getElementById('log');
        const connectBtn = document.getElementById('connect');
        const disconnectBtn = document.getElementById('disconnect');
        const headersContainer = document.getElementById('headers-container');
        const addHeaderBtn = document.getElementById('add-header');

        // 常用请求头列表
        const commonHeaders = [
            'Accept',
            'Accept-Encoding',
            'Accept-Language',
            'Authorization',
            'Cache-Control',
            'Connection',
            'Content-Disposition',
            'Content-Encoding',
            'Content-Length',
            'Content-Type',
            'Cookie',
            'Host',
            'Origin',
            'Pragma',
            'Referer',
            'User-Agent',
            'X-Requested-With'
        ];

        // 常用 Content-Type 值
        const contentTypes = [
            'text/event-stream',
            'application/json',
            'application/x-www-form-urlencoded',
            'multipart/form-data',
            'text/plain',
            'text/html',
            'application/xml'
        ];

        // 添加一行请求头
        function addHeaderRow(name = '', value = '') {
            const headerRow = document.createElement('div');
            headerRow.className = 'header-row';

            const headerNameSelect = document.createElement('select');
            headerNameSelect.className = 'header-name';
            
            // 添加空选项
            const emptyOption = document.createElement('option');
            emptyOption.value = '';
            emptyOption.textContent = '-- 选择请求头 --';
            headerNameSelect.appendChild(emptyOption);
            
            // 添加常用请求头选项
            commonHeaders.forEach(header => {
                const option = document.createElement('option');
                option.value = header;
                option.textContent = header;
                if (header === name) {
                    option.selected = true;
                }
                headerNameSelect.appendChild(option);
            });

            // 为 Content-Type 添加特殊处理
            headerNameSelect.addEventListener('change', function() {
                const valueInput = this.parentNode.querySelector('.header-value');
                if (this.value === 'Content-Type') {
                    // 替换输入框为下拉列表
                    if (valueInput.tagName !== 'SELECT') {
                        const valueSelect = document.createElement('select');
                        valueSelect.className = 'header-value';
                        contentTypes.forEach(type => {
                            const option = document.createElement('option');
                            option.value = type;
                            option.textContent = type;
                            valueSelect.appendChild(option);
                        });
                        valueInput.parentNode.replaceChild(valueSelect, valueInput);
                    }
                } else if (valueInput.tagName === 'SELECT') {
                    // 替换下拉列表为输入框
                    const valueInputNew = document.createElement('input');
                    valueInputNew.type = 'text';
                    valueInputNew.className = 'header-value';
                    valueInputNew.value = valueInput.value;
                    valueInput.parentNode.replaceChild(valueInputNew, valueInput);
                }
            });

            let valueElement;
            if (name === 'Content-Type') {
                valueElement = document.createElement('select');
                valueElement.className = 'header-value';
                contentTypes.forEach(type => {
                    const option = document.createElement('option');
                    option.value = type;
                    option.textContent = type;
                    if (type === value) {
                        option.selected = true;
                    }
                    valueElement.appendChild(option);
                });
            } else {
                valueElement = document.createElement('input');
                valueElement.type = 'text';
                valueElement.className = 'header-value';
                valueElement.value = value;
                valueElement.placeholder = '请求头的值';
            }

            const removeBtn = document.createElement('button');
            removeBtn.className = 'remove-header';
            removeBtn.textContent = '删除';
            removeBtn.addEventListener('click', function() {
                headerRow.remove();
            });

            headerRow.appendChild(headerNameSelect);
            headerRow.appendChild(valueElement);
            headerRow.appendChild(removeBtn);
            headersContainer.appendChild(headerRow);
        }

        // 获取所有请求头
        function getHeaders() {
            const headers = {};
            document.querySelectorAll('.header-row').forEach(row => {
                const name = row.querySelector('.header-name').value;
                const value = row.querySelector('.header-value').value;
                if (name && value) {
                    headers[name] = value;
                }
            });
            return headers;
        }

        // 初始化添加几个常用请求头
        addHeaderRow('Accept', '*/*');
        addHeaderRow('Content-Type', 'text/event-stream');
        addHeaderRow('Cache-Control', 'no-cache');
        addHeaderRow('Connection', 'keep-alive');

        // 添加新请求头的事件
        addHeaderBtn.addEventListener('click', () => {
            addHeaderRow();
        });

        function log(message, type = '') {
            const entry = document.createElement('div');
            entry.className = `log-entry ${type}`;
            entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
            logContainer.appendChild(entry);
            logContainer.scrollTop = logContainer.scrollHeight;
        }

        function connect() {
            try {
                const url = document.getElementById('url').value;
                const method = document.getElementById('method').value;
                const headers = getHeaders();
                const body = document.getElementById('body').value;

                const controller = new AbortController();
                const signal = controller.signal;

                // 创建请求选项
                const fetchOptions = {
                    method: method,
                    headers: headers,
                    signal: signal
                };

                // 只有非 GET/HEAD 方法才能包含请求体
                if (method !== 'GET' && method !== 'HEAD' && body) {
                    fetchOptions.body = body;
                }

                log(`发起${method}请求: ${url}`, 'success');
                
                fetch(url, fetchOptions).then(response => {
                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    
                    log(`收到响应: HTTP ${response.status} ${response.statusText}`, 'success');
                    
                    // 检查内容类型
                    const contentType = response.headers.get('Content-Type');
                    log(`响应内容类型: ${contentType || '未指定'}`, 'success');
                    
                    // 如果是 SSE 类型，建立流式连接
                    if (contentType && contentType.includes('text/event-stream')) {
                        log('检测到 SSE 连接，建立流式处理...', 'success');
                        
                        const reader = response.body.getReader();
                        const decoder = new TextDecoder();

                        function readStream() {
                            reader.read().then(({done, value}) => {
                                if (done) {
                                    log('SSE 连接已关闭', 'error');
                                    return;
                                }

                                const chunk = decoder.decode(value);
                                const lines = chunk.split('\n');
                                
                                lines.forEach(line => {
                                    if (line.startsWith('data:')) {
                                        const data = line.slice(5).trim();
                                        log(`收到消息: ${data}`, 'success');
                                    } else if (line.startsWith('event:')) {
                                        const event = line.slice(6).trim();
                                        log(`事件类型: ${event}`, 'success');
                                    } else if (line.startsWith('id:')) {
                                        const id = line.slice(3).trim();
                                        log(`消息ID: ${id}`, 'success');
                                    } else if (line.startsWith('retry:')) {
                                        const retry = line.slice(6).trim();
                                        log(`重连时间: ${retry}ms`, 'success');
                                    } else if (line.trim()) {
                                        log(`原始数据: ${line}`, 'success');
                                    }
                                });

                                readStream();
                            }).catch(error => {
                                log(`读取错误: ${error.message}`, 'error');
                            });
                        }

                        readStream();
                        
                    } else {
                        // 普通响应，显示响应内容
                        log('接收普通 HTTP 响应，显示响应内容...', 'success');
                        
                        response.text().then(text => {
                            try {
                                // 尝试解析为 JSON
                                const json = JSON.parse(text);
                                log(`响应内容(JSON): ${JSON.stringify(json, null, 2)}`, 'success');
                            } catch {
                                // 不是 JSON，显示原始文本
                                log(`响应内容: ${text}`, 'success');
                            }
                        });
                    }

                    // 保存控制器以便后续断开连接
                    eventSource = controller;
                    connectBtn.disabled = true;
                    disconnectBtn.disabled = false;
                    
                }).catch(error => {
                    log(`连接错误: ${error.message}`, 'error');
                });
            } catch (error) {
                log(`配置错误: ${error.message}`, 'error');
            }
        }

        function disconnect() {
            if (eventSource) {
                eventSource.abort();
                eventSource = null;
                connectBtn.disabled = false;
                disconnectBtn.disabled = true;
                log('已断开连接', 'success');
            }
        }

        connectBtn.addEventListener('click', connect);
        disconnectBtn.addEventListener('click', disconnect);
    </script>
</body>
</html>